﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RoutingDaemon.Entities;
using IRC.Utilities.Entities;

namespace RoutingDaemon.Backend
{
    /// <summary>
    /// Routing Daemon Backend
    /// </summary>
    public class DaemonBackEnd
    {
        /// <summary>
        /// Static instance (Singleton).
        /// </summary>
        private static DaemonBackEnd instance;

        private const int DefaultVersion = 1;
        private const int DefaultTTL = 32;

        private List<Node> allNodes;

        private DaemonBackEnd() 
        {
            allNodes = new List<Node>();
            LocalNode = new Node();
            RoutingTable = new List<RoutingTableEntry>();
        }

        /// <summary>
        /// Gets the instance.
        /// </summary>
        public static DaemonBackEnd Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new DaemonBackEnd();
                }
                return instance;
            }
        }

        public static void Reset()
        {
            instance = null;
        }

        /// <summary>
        /// Gets or sets the local node.
        /// </summary>
        /// <value>
        /// The local node.
        /// </value>
        public Node LocalNode { get; set; }

        /// <summary>
        /// Gets or sets the routing table.
        /// </summary>
        /// <value>
        /// The routing table.
        /// </value>
        public List<RoutingTableEntry> RoutingTable { get; set; }

        /// <summary>
        /// Gets or sets the configuration of the local node.
        /// </summary>
        /// <value>
        /// The configuration of the node.
        /// </value>
        public List<NodeConfiguration> Configuration { get; set; }

        /// <summary>
        /// Updates the routing table using Dijkstra.
        /// </summary>
        public void UpdateRoutingTable()
        {
            DijkstraMapper mapper = new DijkstraMapper(allNodes.Where(x=>!x.IsDown).ToList());
            mapper.Run();
            this.RoutingTable = mapper.GetRoutingTable();
        }

        /// <summary>
        /// Gets the node by ID. If the node doesn't exist it creates it.
        /// </summary>
        /// <param name="nodeID">The node ID.</param>
        /// <returns>The Node object.</returns>
        public Node GetNodeByID(int nodeID)
        {
            Node node = this.allNodes.FirstOrDefault(n => n.NodeID == nodeID);

            if (node == null)
            {
                node = new Node() { NodeID = nodeID };
                this.allNodes.Add(node);
            }

            return node;
        }

        /// <summary>
        /// Updates the network with a newly received LSA (Link State Announcement).
        /// </summary>
        /// <param name="lsa">The LSA.</param>
        /// <returns>Null on successful update, and an LSA if the sequence number is older than the latest received from that sender.</returns>
        public LSA UpdateBackEndWithLSA(LSA lsa)
        {
            Node senderNode = this.allNodes.FirstOrDefault(n=>n.NodeID == lsa.SenderNodeID);

            if (senderNode == null)
            {
                senderNode = new Node()
                {
                    NodeID = lsa.SenderNodeID,
                    LastSequenceNumber = lsa.SequenceNumber,
                    Neighbors = lsa.Links,
                    Users = lsa.Users
                };
            }
            else if (senderNode == this.LocalNode)
            {
                this.LocalNode.LastSequenceNumber = lsa.SequenceNumber;
                this.LocalNode.Neighbors = lsa.Links;
                this.LocalNode.Users = lsa.Users;
            }
            else
            {
                if (senderNode.LastSequenceNumber >= lsa.SequenceNumber)
                {
                    return new LSA()
                    {
                        SenderNodeID = senderNode.NodeID,
                        SequenceNumber = senderNode.LastSequenceNumber,
                        Users = senderNode.Users,
                        Version = DefaultVersion,
                        Links = senderNode.Neighbors
                    };
                }
                else
                {
                    senderNode.LastSequenceNumber = lsa.SequenceNumber;
                    senderNode.Neighbors = lsa.Links;
                    senderNode.Users = lsa.Users;
                }
            }

            this.UpdateRoutingTable();

            return null;
        }

        /// <summary>
        /// Configures the local node using the info in the configuration file.
        /// </summary>
        /// <param name="configuration">The configuration loaded from the configuration file.</param>
        public void ConfigureLocalNode(List<NodeConfiguration> configuration)
        {
            foreach (NodeConfiguration node in configuration)
            {
                if (node.NodeID != this.LocalNode.NodeID)
                {
                    if (this.LocalNode.Neighbors.Where(n => n.NodeID == node.NodeID).Count() == 0)
                    {
                        this.LocalNode.Neighbors.Add(new Node()
                        {
                            NodeID = node.NodeID,
                            Configuration = node
                        });
                    }
                }
                else
                {
                    this.LocalNode.Configuration = node;
                }
            }

            this.Configuration = configuration;
            this.allNodes = this.GetAllNodes(this.LocalNode);
        }

        /// <summary>
        /// Gets the local node LSA.
        /// </summary>
        /// <returns>The LSA of the local node.</returns>
        public LSA GetLocalNodeLSA()
        {
            return new LSA()
            {
                Version = DefaultVersion,
                TTL = DefaultTTL,
                SenderNodeID = this.LocalNode.NodeID,
                SequenceNumber = this.LocalNode.LastSequenceNumber,
                Users = this.LocalNode.Users,
                Links = this.LocalNode.Neighbors.Where(n=>!n.IsDown).ToList(),
                Type = LSAType.Advertisement
            };
        }

        /// <summary>
        /// Gets all nodes.
        /// </summary>
        /// <param name="rootNode">The root node.</param>
        /// <returns>List of all nodes mapped.</returns>
        private List<Node> GetAllNodes(Node rootNode)
        {
            List<Node> nodes = new List<Node>() { rootNode };

            ExploreNode(rootNode, nodes);

            return nodes;
        }

        /// <summary>
        /// Explores the node recursively updating the list of nodes.
        /// </summary>
        /// <param name="node">The node to explore.</param>
        /// <param name="nodes">The nodes list to update.</param>
        private void ExploreNode(Node node, List<Node> nodes)
        {
            foreach (Node neighbor in node.Neighbors)
            {
                if (!nodes.Contains(neighbor))
                {
                    nodes.Add(neighbor);
                    ExploreNode(neighbor, nodes);
                }
            }
        }
    }
}
